package com.topnav.mc.listener;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.js.lib.cache.Cache;
import com.js.lib.utils.DateUtils;
import com.topnav.mc.admin.bean.cachebean.CacheDeviceFenceBean;
import com.topnav.mc.admin.bean.cachebean.CacheFencePolygonBean;
import com.topnav.mc.admin.bean.cachebean.CacheOrgBean;
import com.topnav.mc.admin.bean.cachebean.EnumDeviceFenceStatus;
import com.topnav.mc.admin.entity.*;
import com.topnav.mc.admin.entity.bean.DeviceAlarmBean;
import com.topnav.mc.admin.entity.enums.DeviceAlarmCate;
import com.topnav.mc.admin.entity.enums.EnumCoordinateSystem;
import com.topnav.mc.admin.entity.enums.FenceAlarmType;
import com.topnav.mc.admin.entity.param.DeviceAlarmParam;
import com.topnav.mc.admin.service.*;
import com.topnav.mc.lock.SyncLock;
import com.topnav.mc.listener.bean.GPSMsgInfo;
import com.topnav.mc.position.PositionInfo;
import com.topnav.mc.position.PositionUtil;
import com.topnav.mc.sse.DeviceAlarmQueue;
import com.topnav.mc.sse.EnumSseMessageType;
import com.topnav.mc.sse.MessageBean;
import com.topnav.mc.uitls.gps.Gps;
import com.topnav.mc.uitls.gps.GpsUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;
import javax.annotation.Resource;
import javax.validation.constraints.NotNull;
import com.alibaba.fastjson2.JSON;
import org.springframework.util.StringUtils;

/**
 * 接收来自redis的GPS更新通知
 * @author lin
 */
@Component
@Slf4j
public class RedisGpsMsgListener implements MessageListener {

    private final int repeatTimes = 2; //重复多少次才报警
    @Resource(name="sysCache")
    private Cache sysCache;
    @Autowired
    private PositionUtil positionUtil;
    @Autowired
    private IOrganizationService organizationService;
    @Autowired
    private IFenceService fenceService;
    @Autowired
    private IDeviceAlarmService deviceAlarmService;
    @Autowired
    private IUserService userService;
    @Autowired
    private DeviceAlarmQueue deviceAlarmQueue;
    private ConcurrentLinkedQueue<GPSMsgInfo> taskQueue = new ConcurrentLinkedQueue<>();
    @Autowired
    private IDevicePositionService positionService;
    @Autowired
    private IDeviceService deviceService;
    @Autowired
    private IDeviceChannelService channelService;
    @Autowired
    private IFenceAlarmService fenceAlarmService;



    //private static String TEMP_DEVICE_POSITON = "TEMP_DEVICE_POSITON";//临时保存定位信息

    //private ConcurrentLinkedQueue<Message> taskQueue = new ConcurrentLinkedQueue<>();


    @Override
    public void onMessage(@NotNull Message message, byte[] bytes) {
        GPSMsgInfo gpsMsgInfo = JSON.parseObject(message.getBody(), GPSMsgInfo.class);
        if (gpsMsgInfo.getLongitude() == 0){
            log.error(">>>>> 收到不规范的定位信息：code:{}, lng：{}，lat: {} 不处理",gpsMsgInfo.getSerial(),gpsMsgInfo.getLongitude(),gpsMsgInfo.getLatitude());
            return;
        }else{
            log.info(">>>>> 收到定位信息：code:{}, lng：{}，lat: {}",gpsMsgInfo.getSerial(),gpsMsgInfo.getLongitude(),gpsMsgInfo.getLatitude());
        }

        taskQueue.offer(gpsMsgInfo);
        this.consusGps();
    }

    /**
     * 从报警上来的GPS
     * @param gpsMsgInfo
     */
    public void alarmGps(GPSMsgInfo gpsMsgInfo ){
        taskQueue.offer(gpsMsgInfo);
        this.consusGps();
    }

    /**
     * 消费队列里的数据
     */
    @Async("taskExecutor")
    @SyncLock(key="RedisGpsMsgListene-consusGps-LOCK",dueSec = 100*60,errorUnLock = true)
    public void consusGps(){
        Device device = null;
        GPSMsgInfo gpsMsgInfo = null;
        List<DevicePosition> devicePositions = null;
        DevicePosition devicePosition = null;
        try{
            while (!taskQueue.isEmpty()) {
                devicePosition = null;
                gpsMsgInfo = taskQueue.poll();

                //设置为当前定位信息
                if (0.0 == gpsMsgInfo.getLongitude()){
                    continue;
                }
                if ((device = deviceService.getFromCache(gpsMsgInfo.getSerial())) == null){
                    continue;
                }

                PositionInfo positionInfo = new PositionInfo();
                positionInfo.setDeviceCode(gpsMsgInfo.getSerial());
                positionInfo.setLongitude(gpsMsgInfo.getLongitude());
                positionInfo.setLatitude(gpsMsgInfo.getLatitude());
                positionInfo.setPositionTime(DateUtils.parse(gpsMsgInfo.getTime(),"yyyy-MM-dd HH:mm:ss"));
                positionInfo.setCoordinateSystem(device.getCoordinateSystem()); //坐标系
                positionUtil.setCurrentPosition(positionInfo);

                //取此设备最后的定位
                device.setLongitude(positionInfo.getLongitude());
                device.setLatitude(positionInfo.getLatitude());
                deviceService.saveToCache(device);

                if (devicePositions == null){devicePositions = new ArrayList<DevicePosition>();}

                try{
                    devicePosition = this.genDevicePosition(gpsMsgInfo,device);
                    if (devicePosition != null){
                        devicePositions.add(devicePosition);
                    }
                }catch (Exception e){
                    continue;
                }
                if (devicePositions.size() >= 50) {
                    //批量存储定位信息
                    this.saveDevicePostitionsToDb(devicePositions);
                    devicePositions.clear();
                }
                //围栏判断
                //当前设备所属部门
                CacheOrgBean cacheOrgBean = organizationService.getFromCache(device.getOrgId());
                //所有围栏，按部门的当前设备部门对比
                List<CacheFencePolygonBean> fencelist = fenceService.getAllFromCache();
                for(CacheFencePolygonBean fencebean : fencelist){
                    Gps gps = null;
                    //按部门判断，设备只要属部门及下级就报警生效
                    if (cacheOrgBean.getPath().startsWith(fencebean.getPath())){
                        //判断坐标系，不一样则转换
                        if (!device.getCoordinateSystem().equals(fencebean.getCoordinateSystem())){
                            if (fencebean.getCoordinateSystem().equals(EnumCoordinateSystem.GCJ02.getCode())){
                                //目前只支持 WGS84和GCJ02 如果围标坐是 GCJ02则设备一定属于 WGS84
                                if (device.getCoordinateSystem().equals(EnumCoordinateSystem.WGS84.getCode())){
                                    gps = GpsUtils.gps84ToGcj02(gpsMsgInfo.getLatitude(),gpsMsgInfo.getLongitude());
                                }else{
                                    log.error(">>>>> 暂不支持设备的坐标系: {}",device.getCoordinateSystem());
                                }
                            }else{
                                if (device.getCoordinateSystem().equals(EnumCoordinateSystem.GCJ02.getCode())){
                                    gps = GpsUtils.gcj02ToGps84(gpsMsgInfo.getLatitude(),gpsMsgInfo.getLongitude());
                                }else{
                                    log.error(">>>>> 暂不支持设备的坐标系: {}",device.getCoordinateSystem());
                                }
                            }
                        }else{
                            gps = new Gps(gpsMsgInfo.getLongitude(),gpsMsgInfo.getLatitude());
                        }

                        if (gps != null){
                            // 如果gps==null就不做报警处理
                            CacheDeviceFenceBean cdfBean= fenceService.getDeviceFenceStatusFromCache(
                                    device.getId().toString() + "-" + fencebean.getId().toString());
                            //当前是否在围栏内
                            Integer inPolyon=GpsUtils.PointInPolygon(gps,fencebean.getCoordinates())==true?
                                    EnumDeviceFenceStatus.FENCEIN.getCode():EnumDeviceFenceStatus.FENCEOUT.getCode();
                            if (cdfBean==null){
                                //第一次上线，不报警，count=0, 在栏内或栏外同时多次后再报警
                                fenceService.setDeviceFenceStatusToCache(
                                        new CacheDeviceFenceBean().setDeviceId(device.getId())
                                                .setFenceId(fencebean.getId())
                                                .setTime(new Date())
                                                .setStatus(inPolyon).setCount(0));

                            }else if (inPolyon != cdfBean.getStatus()){
                                //第一次保存或第一次有变化重新保存
                                fenceService.setDeviceFenceStatusToCache(
                                new CacheDeviceFenceBean().setDeviceId(device.getId())
                                        .setFenceId(fencebean.getId())
                                        .setTime(new Date())
                                        .setStatus(inPolyon).setCount(1));
                            }else if (inPolyon == cdfBean.getStatus()){
                                //没有变化, 累计两次才算是报警
                                if (cdfBean.getCount() == this.repeatTimes-1){
                                    //进入围栏或出围栏报警
                                    DeviceAlarm deviceAlarm = new DeviceAlarm();
                                    deviceAlarm.setDeviceId(device.getId());
                                    //类型为SOS
                                    deviceAlarm.setAlarmCate(
                                            inPolyon == EnumDeviceFenceStatus.FENCEIN.getCode()?DeviceAlarmCate.FENCEIN.getCode():DeviceAlarmCate.FENCEOUT.getCode());
                                    DeviceChannel dc = channelService.getChannelFromDevAndChnCode(device.getId(),gpsMsgInfo.getCode());
                                    if (dc != null){
                                        deviceAlarm.setChannelId(dc.getId());
                                    }
                                    deviceAlarm.setAlarmPriority("1");
                                    deviceAlarm.setAlarmTime(new Date());
                                    deviceAlarm.setCreateTime(new Date());
                                    deviceAlarm.setUpdateTime(new Date());
                                    deviceAlarm.setAlarmDescription(inPolyon == EnumDeviceFenceStatus.FENCEIN.getCode()?DeviceAlarmCate.FENCEIN.getDesc():DeviceAlarmCate.FENCEOUT.getDesc());
                                    deviceAlarm.setLongitude(gpsMsgInfo.getLongitude());
                                    deviceAlarm.setLatitude(gpsMsgInfo.getLatitude());
                                    deviceAlarm.setAlarmEvent(inPolyon);
                                    deviceAlarmService.save(deviceAlarm);

                                    //围栏报警数据
                                    FenceAlarm fenceAlarm = new FenceAlarm();
                                    fenceAlarm.setAlarmTime(new Date());
                                    fenceAlarm.setFenceId(cdfBean.getFenceId());
                                    fenceAlarm.setDeviceId(device.getId());
                                    fenceAlarm.setCreateTime(new Date());
                                    fenceAlarm.setUpdateTime(new Date());
                                    fenceAlarm.setAlarmType(inPolyon);
                                    fenceAlarm.setLongitude(gpsMsgInfo.getLongitude());
                                    fenceAlarm.setLatitude(gpsMsgInfo.getLatitude());
                                    fenceAlarmService.save(fenceAlarm);
                                    //fenceAlarm.setAlarmMessage(msg)

                                    //先写数据库再查询
                                    Thread.sleep(50L);
                                    //加入推送队列
                                    if (device.getUserId() != null || device.getOrgId() != null) {

                                        //查询完整信息
                                        QueryWrapper<DeviceAlarmParam> queryWrapper = new QueryWrapper<DeviceAlarmParam>().eq("a.id", deviceAlarm.getId());
                                        DeviceAlarmBean deviceAlarmBean = deviceAlarmService.getOneInfo(queryWrapper, false);
                                        QueryWrapper<Fence> fenceQueryWrapper = new QueryWrapper<>();
                                        fenceQueryWrapper.eq("id",cdfBean.getFenceId());
                                        Fence fence = fenceService.getOne(fenceQueryWrapper,false);
                                        String msg = String.format("%s %s %s 的设备(%s)在(%f,%f)位置 %s <%s> 时报警",
                                                DateUtils.format(deviceAlarm.getAlarmTime(),"yyyy-MM-dd HH:mm:ss"),
                                                deviceAlarmBean.getShortName(),
                                                deviceAlarmBean.getUserName(),
                                                deviceAlarmBean.getDeviceCode(),
                                                deviceAlarmBean.getLongitude(),
                                                deviceAlarmBean.getLatitude(),
                                                deviceAlarmBean.getAlarmCateDesc(),
                                                fence.getFenceName());
                                        UpdateWrapper<DeviceAlarm> updateWrapper = new UpdateWrapper<>();
                                        updateWrapper.set("alarm_message",msg).eq("id",deviceAlarm.getId());
                                        deviceAlarmService.update(updateWrapper);

                                        deviceAlarmBean.setAlarmMessage(msg);
                                        MessageBean messageBean = new MessageBean(EnumSseMessageType.ALARM_NOTIFY);
                                        messageBean.setMessage(deviceAlarmBean);

                                        if (device.getUserId() != null) {
                                            User user = userService.getById(device.getUserId());
                                            messageBean.setUserId(user.getId());
                                            messageBean.setUserName(user.getName());
                                        }
                                        if (device.getOrgId() != null) {
                                            Organization organization = organizationService.getById(device.getOrgId());
                                            messageBean.setOrgId(organization.getId());
                                            messageBean.setOrgPath(organization.getPath());
                                        }
                                        //转入队列
                                        deviceAlarmQueue.offer(messageBean);
                                    }
                                }
                                cdfBean.setTime(new Date());
                                if (cdfBean.getCount() == 0){
                                    cdfBean.setCount(this.repeatTimes); //下一次就跳过不会报警了
                                }else{
                                    cdfBean.setCount(cdfBean.getCount()+1);
                                }

                                fenceService.setDeviceFenceStatusToCache(cdfBean);
                            }
                        }

                    }
                }
                Thread.sleep(10L);
            }
            if (devicePositions.size() >= 0) {
                this.saveDevicePostitionsToDb(devicePositions);
                devicePositions.clear();
            }
        }catch (Exception e){
            log.error(e.toString());
        }
    }

    private DevicePosition genDevicePosition(GPSMsgInfo gpsMsgInfo,Device device) throws Exception {
        DevicePosition devicePosition = null;
        try {
            devicePosition = new DevicePosition();
            devicePosition.setDeviceId(device.getId());
            devicePosition.setDeviceName(device.getName());
            devicePosition.setDeviceCode(gpsMsgInfo.getSerial());
            devicePosition.setLongitude(gpsMsgInfo.getLongitude());
            devicePosition.setLatitude(gpsMsgInfo.getLatitude());
            devicePosition.setSpeed(gpsMsgInfo.getSpeed());
            devicePosition.setTime(DateUtils.parse(gpsMsgInfo.getTime(),"yyyy-MM-dd HH:mm:ss"));
            if (!StringUtils.isEmpty(gpsMsgInfo.getDirection())){
                devicePosition.setDirection(Double.valueOf(gpsMsgInfo.getDirection()));
            }
            devicePosition.setCoordinateSystem(device.getCoordinateSystem());
            devicePosition.setDirection(0.0);

            devicePosition.setUpdateTime(new Date());
            devicePosition.setCreateTime(new Date());
        }catch (Exception e) {
            log.error(e.toString());
            throw e;
        }
        return devicePosition;
    }

    private void saveDevicePostitionsToDb(List<DevicePosition> devicePositions) throws Exception {
        try {
            positionService.saveBatch(devicePositions); //批量存入数据库
        }catch (Exception e) {
            log.error(">>>> 经纬度保存到定位数据库错误：{}",e.toString());
            throw e;
        }

    }


    /**
     * 定时将当前经纬度更新到设备上并保存到数据库
     */
    @Scheduled(fixedRate = 120 * 1000)   //每100秒执行一次
    @SyncLock(key="SyncUpdateCurrentLocateToDb-LOCK",dueSec = 5*60)
    public void updateCurrentLocateToDb(){
        try{
            UpdateWrapper<Device> updateWrapper = new UpdateWrapper<>();
            List<PositionInfo> positionInfoList = positionUtil.getAllCurrentPositionList();

            for(PositionInfo item : positionInfoList){
                Device device = deviceService.getFromCache(item.getDeviceCode());

                if (device != null){
                    updateWrapper.clear();
                    updateWrapper.eq("id",device.getId());
                    updateWrapper.set("longitude",item.getLongitude());
                    updateWrapper.set("latitude",item.getLatitude());
                    updateWrapper.set("position_time",item.getPositionTime());
                    deviceService.update(updateWrapper);

                }
            }
        }catch (Exception e){
            log.error(">>>> 最新经纬度更新到设备数据库错误：{}",e.toString());
        }
    }


}
